package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"slices"
	"sort"
	"strings"

	"github.com/vektah/gqlparser/v2"
	"github.com/vektah/gqlparser/v2/ast"
)

func main() {
	out := flag.String("out", "", "Output file.")
	flag.Parse()
	log.SetFlags(log.Lshortfile)
	var src []*ast.Source
	for _, pattern := range flag.Args() {
		files, err := filepath.Glob(pattern)
		if err != nil {
			log.Fatal("ERROR:", err)
		}
		for _, file := range files {
			data, err := os.ReadFile(file)
			if err != nil {
				log.Fatal("ERROR:", err)
			}
			src = append(src, &ast.Source{
				Name:  file,
				Input: string(data),
			})
		}
	}

	sch, err := gqlparser.LoadSchema(src...)
	if err != nil {
		log.Fatal("ERROR:", err)
	}

	w := os.Stdout
	if *out != "" {
		fd, err := os.Create(*out)
		if err != nil {
			log.Fatal("ERROR:", err)
		}
		defer fd.Close()
		w = fd
	}

	typeName := func(n *ast.Type) string {
		result := ""

		switch n.Name() {
		case "String", "ID":
			result = "string"
		case "Int":
			result = "number"
		case "Boolean":
			result = "boolean"
		default:
			result = n.Name()
		}

		isArrayType := (n.String())[:1] == "["
		if isArrayType {
			result += "[]"
		}

		return result
	}

	fmt.Fprintf(w, "// Code generated by devtools/gqltsgen DO NOT EDIT.\n\n")

	var types []*ast.Definition
	for _, typ := range sch.Types {
		types = append(types, typ)
	}

	// sort by name to ensure consistent output
	slices.SortFunc(types, func(a, b *ast.Definition) int {
		if a.Name == b.Name {
			return 0
		}

		if a.Name < b.Name {
			return -1
		}

		return 1
	})

	for _, def := range types {
		switch def.Kind {
		case ast.Enum:
			fmt.Fprintf(w, "export type %s = ", def.Name)

			slices.SortFunc(def.EnumValues, func(a, b *ast.EnumValueDefinition) int {
				if a.Name == b.Name {
					return 0
				}

				if a.Name < b.Name {
					return -1
				}

				return 1
			})

			for _, e := range def.EnumValues {
				fmt.Fprintf(w, " | '%s'", e.Name)
			}
			fmt.Fprintf(w, "\n\n")
		case ast.InputObject, ast.Object:
			fmt.Fprintf(w, "export interface %s {\n", def.Name)

			slices.SortFunc(def.Fields, func(a, b *ast.FieldDefinition) int {
				if a.Name == b.Name {
					return 0
				}

				if a.Name < b.Name {
					return -1
				}

				return 1
			})

			for _, e := range def.Fields {
				mod := "?"
				modTypeName := "null | "
				if e.Type.NonNull {
					mod = ""
					modTypeName = ""
				}
				fmt.Fprintf(w, "  %s: %s\n", e.Name+mod, modTypeName+typeName(e.Type))
			}
			fmt.Fprintf(w, "}\n\n")
		case ast.Scalar:
			typeName := "string"
			if def.Name == "WeekdayFilter" {
				typeName = "[boolean, boolean, boolean, boolean, boolean, boolean, boolean]"
			}
			if strings.HasSuffix(def.Name, "Map") {
				typeName = "Record<string, string>"
			}
			fmt.Fprintf(w, "export type %s = %s\n\n", def.Name, typeName)
		case ast.Union:
			sort.Strings(def.Types)
			fmt.Fprintf(w, "export type %s = %s\n\n", def.Name, strings.Join(def.Types, " | "))
		default:
			log.Fatalln("Unsupported kind:", def.Name, def.Kind)
		}
	}
}
